/*
 * Copyright 2013 Walter Lütgenau.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package offset.nodes.client.dialog.navigation.model;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.text.AttributeSet;
import javax.swing.text.Document;
import javax.swing.text.Element;
import netscape.javascript.JSObject;

/**
 *
 * @author Walter Lütgenau
 */
public class JSNode implements Element {

    public static final String M_NODE_NAME = "nodeName";
    public static final String M_NODE_VALUE = "nodeValue";
    public static final String M_VALUE = "value";
    public static final String M_PARENT_NODE = "parentNode";
    public static final String M_TEXT = "#text";
    public static final String M_DATA = "data";
    public static final String M_ATTRIBUTES = "attributes";
    public static final String M_CHILD_NODES = "childNodes";
    public static final String M_LENGTH = "length";
    public static final String M_OFFSET_LEFT = "offsetLeft";
    public static final String M_SCROLL_LEFT = "scrollLeft";
    public static final String M_CLIENT_LEFT = "clientLeft";
    public static final String M_OFFSET_TOP = "offsetTop";
    public static final String M_SCROLL_TOP = "scrollTop";
    public static final String M_CLIENT_TOP = "clientTop";
    public static final String M_OFFSET_PARENT = "offsetParent";
    public static final String M_OFFSET_HEIGHT = "offsetHeight";
    public static final String M_SCROLL_HEIGHT = "scrollHeight";
    public static final String M_CLIENT_HEIGHT = "clientHeight";
    public static final String M_OFFSET_WIDTH = "offsetWidth";
    public static final String M_SCROLL_WIDTH = "scrollWidth";
    public static final String M_CLIENT_WIDTH = "clientWidth";
    
    public static final String ATT_VALUE = "value";
    static private final Pattern arrayPattern = Pattern.compile("([a-zA-Z]*)\\[([0-9]*)\\]");
    static private final String ARRAY_START = "[";
    JSObject object;

    public JSNode(JSObject object) {
        this.object = object;
    }

    /**
     * Return the node denoted by this path or null. The path consists of path components, separated
     * by dots. Each component is either a string or an array component.
     *
     * Example: parent.contentFrame.document.applets[0]
     *
     * @param path the path
     * @return the node, if present, or null
     */
    public JSNode getNode(String path) {
        String[] components = path.split("\\.");

        JSObject current = this.getObject();
        for (String component : components) {
            if (component.indexOf(ARRAY_START) >= 0) {
                Matcher matcher = arrayPattern.matcher(component);
                if (!matcher.matches())
                    return null;

                current = (JSObject) current.getMember(matcher.group(1));
                if (current == null)
                    return null;
                int index = -1;
                try {
                    index = Integer.parseInt(matcher.group(2));
                } catch (NumberFormatException e) {
                }

                if (index < 0)
                    return null;

                current = (JSObject) current.getSlot(index);

                continue;
            }

            current = (JSObject) current.getMember(component);
            if (current == null)
                return null;
        }

        return new JSNode(current);
    }

    /**
     * Is this a text node?
     * 
     * @return the answer
     */
    public boolean isText() {
        String name = getName();
        return name.equals(M_TEXT);
    }

    /**
     * Return the text, if this is a text component or null.
     *
     * @return the text or null
     */
    public String getText() {
        StringBuilder buf = new StringBuilder();

        for (int i = 0; i < getElementCount(); i++) {
            Element e = getElement(i);

            String name = e.getName();
            if (name.equals(M_TEXT)) {
                Object nodeValue = ((JSNode) e).getObject().getMember(M_NODE_VALUE);
                buf.append(nodeValue.toString());
            }
        }

        return buf.toString().trim();
    }
    
    public int getOffsetLeft() {
        return JSUtil.getInt(getObject(), M_OFFSET_LEFT);
    }
    
    public int getScrollLeft() {
        return JSUtil.getInt(getObject(), M_SCROLL_LEFT);
    }
    
    public int getClientLeft() {
        return JSUtil.getInt(getObject(), M_CLIENT_LEFT);
    }
    
    public int getOffsetTop() {
        return JSUtil.getInt(getObject(), M_OFFSET_TOP);
    }
    
    public int getScrollTop() {
        return JSUtil.getInt(getObject(), M_SCROLL_TOP);
    }
    
    public int getClientTop() {
        return JSUtil.getInt(getObject(), M_CLIENT_TOP);
    }
    
    public int getOffsetWidth() {
        return JSUtil.getInt(getObject(), M_OFFSET_WIDTH);
    }
    
    public int getScrollWidth() {
        return JSUtil.getInt(getObject(), M_SCROLL_WIDTH);
    }
    
    public int getClientWidth() {
        return JSUtil.getInt(getObject(), M_CLIENT_WIDTH);
    }
    
   public int getOffsetHeight() {
       return JSUtil.getInt(getObject(), M_OFFSET_HEIGHT);
    }
    
    public int getScrollHeight() {
        return JSUtil.getInt(getObject(), M_SCROLL_HEIGHT);
    }
    
    public int getClientHeight() {
        return JSUtil.getInt(getObject(), M_CLIENT_HEIGHT);
    }
    
    public JSNode getOffsetParent() {
        Object obj = getObject().getMember(M_OFFSET_PARENT);
        if (obj == null)
            return null;
        
        return new JSNode((JSObject) obj);
    }
    
    public JSNode getParentNode() {
        Object obj = getObject().getMember(M_PARENT_NODE);
        if (obj == null)
            return null;

        return new JSNode((JSObject) obj);
    }

    
    
    /*** Element interface methods */

    @Override
    public Document getDocument() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public Element getParentElement() {
        return getParentNode();
    }

    @Override
    public String getName() {
        Object obj = getObject().getMember(M_NODE_NAME);
        if (obj == null)
            return null;

        return obj.toString().toLowerCase();
    }

    @Override
    public AttributeSet getAttributes() {
        return new JSAttributeSet((JSObject) getObject().getMember(M_ATTRIBUTES));
    }

    @Override
    public int getStartOffset() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public int getEndOffset() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public int getElementIndex(int offset) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public int getElementCount() {
        JSObject childNodes = (JSObject) getObject().getMember(M_CHILD_NODES);
        if (childNodes == null)
            return 0;

        return JSUtil.getInt(childNodes, M_LENGTH);
    }

    @Override
    public Element getElement(int index) {
        JSObject childNodes = (JSObject) getObject().getMember(M_CHILD_NODES);
        if (childNodes == null)
            return null;

        return new JSNode((JSObject) childNodes.getSlot(index));
    }

    @Override
    public boolean isLeaf() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    /**
     * The wrapped JSObject.
     *
     * @return the JSObject
     */
    public JSObject getObject() {
        return object;
    }
    
    public String toString() {
        return super.toString() + getObject().toString();
    }
}
